import tkinter as tk
from tkinter import messagebox, ttk, scrolledtext, Menu, font
import requests
import threading

# Путь к файлу с токенами
TOKENS_FILE_PATH = "C:\\Users\\1\\Desktop\\VS 2022\\Настройки аккаунтов ВК\\Токены для настроек ВК.txt"
# Путь к файлу для сохранения закрытых аккаунтов
CLOSED_PROFILES_FILE_PATH = "C:\\Users\\1\\Desktop\\VS 2022\\Настройки аккаунтов ВК\\закрытые аккаунты.txt"

# Цвет для строки с закрытым профилем
CLOSED_PROFILE_COLOR = "#ffcccc"  # Светло-красный цвет

# Максимальное количество сообщений в журнале
MAX_LOG_MESSAGES = 50

# Цвета для различных типов сообщений
colors = {
    "info": "#333333",      # Темно-серый цвет для информации
    "success": "#008000",   # Зеленый цвет для успешных действий
    "warning": "#FFA500",   # Оранжевый цвет для предупреждений
    "error": "#FF0000"      # Красный цвет для ошибок
}

# Словарь для сопоставления английских значений с русскими
category_translation = {
    'all': 'Все',
    'friends_of_friends': 'Друзья друзей',
    'friends': 'Друзья',
    'only_me': 'Только я',
    'some': 'Некоторые',
    'nobody': 'Никто',
    'all_except_of_search_engines': 'Все, кроме поисковых систем',
    'vk_users_only': 'Только пользователи VK',
    'see_all_friends': 'Все друзья',
    'closed': 'Закрытый профиль',
    'open': 'Открытый профиль'
}

# Обратный словарь для преобразования русских значений обратно в английские
reverse_category_translation = {v: k for k, v in category_translation.items()}

# Функция для перевода значений на русский
def translate_category(category, reverse=False):
    if reverse:
        return reverse_category_translation.get(category, category)
    else:
        return category_translation.get(category, category)

# Функция для записи в журнал с минималистичным оформлением
def log_message(message, msg_type="info"):
    # Иконки или символы для разных типов сообщений
    icons = {
        "info": "ℹ️",
        "success": "✅",
        "warning": "⚠️",
        "error": "❌"
    }

    # Обрезка длинных сообщений и добавление многоточия
    max_length = 100
    if len(message) > max_length:
        message = message[:max_length] + "..."

    # Форматирование сообщения
    formatted_message = f"{icons.get(msg_type, '')} {message}"
    
    # Очистка старых сообщений, если превышено максимальное количество
    if log_text.index("end-1c").split('.')[0] != '':
        num_lines = int(log_text.index("end-1c").split('.')[0])
        if num_lines >= MAX_LOG_MESSAGES:
            log_text.delete(1.0, 2.0)  # Удаление самой старой строки
    
    # Запись сообщения в журнал с цветовым оформлением
    log_text.configure(state='normal')  # Разрешить редактирование текстового поля
    log_text.insert(tk.END, formatted_message + '\n', msg_type)
    log_text.configure(state='disabled')  # Запретить редактирование текстового поля
    log_text.yview(tk.END)  # Прокрутить журнал к последнему сообщению

# Функция для применения стилей к журналу
def apply_log_styles():
    log_text.tag_config("info", foreground=colors["info"], font=("Courier", 10))
    log_text.tag_config("success", foreground=colors["success"], font=("Courier", 10))
    log_text.tag_config("warning", foreground=colors["warning"], font=("Courier", 10, "bold"))
    log_text.tag_config("error", foreground=colors["error"], font=("Courier", 10, "bold"))

# Функция для получения списка токенов из файла
def get_tokens_from_file(file_path):
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            tokens = file.read().splitlines()
        log_message(f"Токены успешно загружены из файла: {file_path}", msg_type="success")
        return tokens
    except Exception as e:
        error_msg = f"Ошибка при чтении файла с токенами: {str(e)}"
        log_message(error_msg, msg_type="error")
        return []

# Функция для получения настроек приватности пользователя
def get_privacy_settings(token):
    try:
        url = 'https://api.vk.com/method/account.getPrivacySettings'
        params = {
            'access_token': token,
            'v': '5.131'
        }
        response = requests.get(url, params=params)
        data = response.json()
        if 'error' in data:
            error_msg = f"Ошибка VK API: {data['error']['error_msg']}"
            log_message(error_msg, msg_type="error")
            return None
        return data['response']['settings']
    except Exception as e:
        error_msg = f"Произошла ошибка: {str(e)}"
        log_message(error_msg, msg_type="error")
        return None

# Функция для получения имени и фамилии пользователя
def get_user_name(token):
    try:
        url = 'https://api.vk.com/method/users.get'
        params = {
            'access_token': token,
            'v': '5.131',
            'fields': 'first_name,last_name'
        }
        response = requests.get(url, params=params)
        data = response.json()
        if 'error' in data:
            error_msg = f"Ошибка VK API: {data['error']['error_msg']}"
            log_message(error_msg, msg_type="error")
            return None, None
        if 'response' in data:
            user_info = data['response'][0]
            first_name = user_info.get('first_name', 'Неизвестно')
            last_name = user_info.get('last_name', 'Неизвестно')
            return first_name, last_name
    except Exception as e:
        error_msg = f"Произошла ошибка при получении имени пользователя: {str(e)}"
        log_message(error_msg, msg_type="error")
        return None, None

# Функция для получения типа профиля (открытый или закрытый)
def get_profile_type(token):
    try:
        url = 'https://api.vk.com/method/users.get'
        params = {
            'access_token': token,
            'v': '5.131',
            'fields': 'is_closed'
        }
        response = requests.get(url, params=params)
        data = response.json()
        if 'error' in data:
            error_msg = f"Ошибка VK API: {data['error']['error_msg']}"
            log_message(error_msg, msg_type="error")
            return None
        if 'response' in data:
            user_info = data['response'][0]
            # Определяем "Тип профиля"
            profile_type = "Закрытый" if user_info.get('is_closed', False) else "Открытый"
            return profile_type
    except Exception as e:
        error_msg = f"Произошла ошибка при получении типа профиля: {str(e)}"
        log_message(error_msg, msg_type="error")
        return None

# Функция для добавления данных аккаунта в таблицу
def add_account_to_table(index, user_name, settings, profile_type):
    if not settings:
        log_message(f"Пропуск токена: {user_name} из-за ошибки при получении данных.", msg_type="warning")
        return

    # Словарь для хранения значений настроек
    settings_values = {}
    combobox_data = {}  # Словарь для хранения данных о выпадающих меню

    # Добавление других настроек в таблицу
    for idx, setting in enumerate(settings, 1):
        key = setting.get('key', 'Нет данных')
        title = setting.get('title', 'Нет данных')
        value = setting.get('value', {}).get('category', 'Нет данных')

        # Перевод значений на русский язык
        translated_value = translate_category(value)
        translated_categories = [translate_category(cat) for cat in setting.get('supported_categories', [])]

        # Добавляем только нужные параметры, кроме "page_access"
        if key in relevant_settings:
            settings_values[title] = translated_value
            if translated_categories:
                combobox_data[title] = translated_categories

    # Добавляем тип профиля и доступ к странице внизу
    settings_values["Тип профиля"] = profile_type
    settings_values["Кому в интернете видна моя страница"] = None

    for setting in settings:
        if setting['key'] == 'page_access':
            value = setting.get('value', {}).get('category', 'Нет данных')
            translated_value = translate_category(value)
            settings_values["Кому в интернете видна моя страница"] = translated_value
            break

    # Обновляем заголовки таблицы, включая имя и фамилию и нумерацию строк
    columns = ["№", "Имя и фамилия"] + list(settings_values.keys())
    if not tree["columns"]:
        tree["columns"] = columns
        for column in tree["columns"]:
            tree.heading(column, text=column, anchor="center")
            tree.column(column, width=150, anchor="center")

    # Добавление значений в таблицу с выделением закрытого профиля
    values = [index, user_name] + [settings_values[column] if settings_values[column] else "" for column in tree["columns"] if column not in ["№", "Имя и фамилия"]]
    
    # Сохраняем тег и данные профиля вместе с индексом
    if profile_type == "Закрытый":
        tree.insert("", "end", values=values, tags=("closed_profile",))
    else:
        tree.insert("", "end", values=values, tags=("open_profile",))

    # Добавляем настройки для выпадающих меню (только один раз)
    if not combobox_vars:
        for setting_title, categories in combobox_data.items():
            add_combobox(setting_title, categories)

    log_message(f"Данные аккаунта {user_name} успешно добавлены в таблицу.", msg_type="success")

# Функция для добавления выпадающего меню для изменения настроек
def add_combobox(setting_title, categories):
    row = len(combobox_vars) // 3  # По 3 выпадающих меню в строке
    col = len(combobox_vars) % 3 * 2

    # Метка без слова "Изменить"
    label = tk.Label(combobox_frame, text=setting_title)
    label.grid(row=row, column=col, padx=5, pady=5, sticky='e')

    # Устанавливаем значение по умолчанию "Только я" для определенных настроек
    default_value = "Только я" if setting_title in ["Кто видит чужие записи на моей странице", "Кто может оставлять записи на моей странице"] else categories[0]
    
    var = tk.StringVar(value=default_value)
    combobox_vars[setting_title] = var

    combobox = ttk.Combobox(combobox_frame, textvariable=var, values=categories, state="readonly")
    combobox.grid(row=row, column=col + 1, padx=5, pady=5, sticky='w')
    combobox_list[setting_title] = combobox

# Функция для сортировки таблицы по номеру
def sort_table_by_number():
    # Получаем все данные из таблицы
    all_items = [(int(tree.set(item, "#1")), tree.item(item, "values"), tree.item(item, "tags")) for item in tree.get_children()]
    # Сортируем данные по номеру (первый столбец)
    all_items.sort(key=lambda x: x[0])

    # Очищаем таблицу
    tree.delete(*tree.get_children())

    # Вставляем отсортированные данные обратно в таблицу с сохранением тегов
    for item in all_items:
        tree.insert("", "end", values=item[1], tags=item[2])

    log_message("Таблица успешно отсортирована по нумерации.", msg_type="info")

# Функция для загрузки настроек для каждого аккаунта в отдельном потоке
def load_account_settings_thread(token, index):
    first_name, last_name = get_user_name(token)
    user_name = f"{first_name} {last_name}"
    settings = get_privacy_settings(token)
    profile_type = get_profile_type(token)
    root.after(0, add_account_to_table, index, user_name, settings, profile_type)

# Функция для загрузки настроек в отдельном потоке
def load_settings_thread():
    log_message("Загрузка настроек приватности и типа профиля для всех токенов...", msg_type="info")
    tokens = get_tokens_from_file(TOKENS_FILE_PATH)
    if not tokens:
        log_message("Нет доступных токенов для загрузки настроек.", msg_type="warning")
        return

    # Очистка таблицы перед добавлением новых данных
    tree.delete(*tree.get_children())

    # Очистка фрейма с выпадающими меню
    for widget in combobox_frame.winfo_children():
        widget.destroy()
    combobox_vars.clear()
    combobox_list.clear()

    # Запуск потоков для каждого токена
    threads = []
    for index, token in enumerate(tokens, start=1):
        thread = threading.Thread(target=load_account_settings_thread, args=(token, index))
        thread.start()
        threads.append(thread)

    # Ожидание завершения всех потоков и сортировка таблицы
    for thread in threads:
        thread.join()

    root.after(0, sort_table_by_number)

def save_changes_thread():
    log_message("Сохранение изменений настроек приватности для всех аккаунтов...", msg_type="info")
    tokens = get_tokens_from_file(TOKENS_FILE_PATH)

    if not tokens:
        log_message("Нет доступных токенов для сохранения изменений.", msg_type="warning")
        return

    for token in tokens:
        first_name, last_name = get_user_name(token)
        user_name = f"{first_name} {last_name}"
        
        settings = get_privacy_settings(token)
        if settings is None:
            log_message(f"Ошибка при получении настроек для {user_name}. Пропуск...", msg_type="error")
            continue

        for column, var in combobox_vars.items():
            selected_value = var.get()
            translated_value = translate_category(selected_value, reverse=True)

            # Поиск настройки в `settings`
            for setting in settings:
                if setting['title'] == column:
                    key = setting['key']
                    
                    # Проверка на наличие значения в списке поддерживаемых категорий
                    supported_categories = setting.get('supported_categories', [])
                    if translated_value not in supported_categories:
                        log_message(f"Ошибка: Значение '{translated_value}' не поддерживается для настройки '{setting['title']}'. Поддерживаемые значения: {supported_categories}", msg_type="error")
                        continue
                    
                    # Обновить настройку через VK API
                    result = set_privacy_setting(key, translated_value, token)
                    if result:
                        log_message(f"Настройка '{setting['title']}' успешно обновлена на '{selected_value}' для {user_name}.", msg_type="success")

    # Сообщение о завершении работы после обработки всех токенов
    log_message("Все изменения для всех аккаунтов успешно сохранены. Работа завершена.", msg_type="success")

# Функция для сохранения закрытых аккаунтов в файл
def save_closed_profiles_thread():
    log_message("Сохранение закрытых профилей в файл...", msg_type="info")
    tokens = get_tokens_from_file(TOKENS_FILE_PATH)
    if not tokens:
        log_message("Нет доступных токенов для сохранения закрытых профилей.", msg_type="warning")
        return

    closed_profiles = set()

    # Чтение существующих закрытых профилей из файла
    try:
        with open(CLOSED_PROFILES_FILE_PATH, 'r', encoding='utf-8') as file:
            existing_profiles = file.read().splitlines()
        closed_profiles.update(existing_profiles)
    except FileNotFoundError:
        log_message("Файл с закрытыми профилями не найден, будет создан новый.", msg_type="warning")
    except Exception as e:
        log_message(f"Ошибка при чтении файла закрытых профилей: {str(e)}", msg_type="error")

    # Сохранение новых закрытых профилей
    new_closed_profiles = []
    for token in tokens:
        first_name, last_name = get_user_name(token)
        user_name = f"{first_name} {last_name}"
        profile_type = get_profile_type(token)
        if profile_type == "Закрытый" and user_name not in closed_profiles:
            closed_profiles.add(user_name)
            new_closed_profiles.append(user_name)
            log_message(f"Закрытый профиль добавлен: {user_name}", msg_type="success")

    if new_closed_profiles:
        try:
            with open(CLOSED_PROFILES_FILE_PATH, 'a', encoding='utf-8') as file:
                for profile in new_closed_profiles:
                    file.write(profile + '\n')
            log_message("Все закрытые профили успешно сохранены.", msg_type="success")
        except Exception as e:
            log_message(f"Ошибка при сохранении закрытых профилей: {str(e)}", msg_type="error")
    else:
        log_message("Нет новых закрытых профилей для сохранения.", msg_type="info")

# Функция для изменения типа профиля (открытый/закрытый)
def set_profile_type(is_closed, token):
    try:
        url = 'https://api.vk.com/method/account.setProfileInfo'
        params = {
            'is_closed': is_closed,
            'access_token': token,
            'v': '5.131'
        }
        response = requests.get(url, params=params)
        data = response.json()
        if 'error' in data:
            error_msg = f"Ошибка VK API при изменении типа профиля: {data['error']['error_msg']}"
            log_message(error_msg, msg_type="error")
            return False
        return True
    except Exception as e:
        error_msg = f"Произошла ошибка при изменении типа профиля: {str(e)}"
        log_message(error_msg, msg_type="error")
        return False

# Функция для обновления настройки приватности через VK API
def set_privacy_setting(key, value, token):
    try:
        url = 'https://api.vk.com/method/account.setPrivacy'
        params = {
            'key': key,
            'value': value,
            'access_token': token,
            'v': '5.131'
        }
        response = requests.get(url, params=params)
        data = response.json()
        if 'error' in data:
            error_msg = f"Ошибка VK API при обновлении настройки {key}: {data['error']['error_msg']}"
            log_message(error_msg, msg_type="error")
            return False
        log_message(f"Настройка {key} успешно обновлена на {value}.", msg_type="success")
        return True
    except Exception as e:
        error_msg = f"Произошла ошибка при обновлении настройки {key}: {str(e)}"
        log_message(error_msg, msg_type="error")
        return False

# Функция для копирования выделенного текста из журнала
def copy_log_text(event=None):
    try:
        selected_text = log_text.selection_get()
        root.clipboard_clear()
        root.clipboard_append(selected_text)
    except tk.TclError:
        pass  # Ничего не делать, если нет выделенного текста

# Функция для создания контекстного меню в журнале
def create_context_menu():
    context_menu = Menu(log_text, tearoff=0)
    context_menu.add_command(label="Копировать", command=copy_log_text)
    return context_menu

# Инициализация окна приложения
root = tk.Tk()
root.title("Настройки приватности VK")
root.geometry("1600x600")  # Увеличенная ширина окна

# Рамка для ввода данных
frame_input = tk.Frame(root)
frame_input.pack(pady=10)

# Кнопка для загрузки настроек
button_load = tk.Button(frame_input, text="Загрузить настройки", command=lambda: threading.Thread(target=load_settings_thread).start())
button_load.pack(side=tk.LEFT, padx=5)

# Кнопка для сохранения изменений
button_save = tk.Button(frame_input, text="Сохранить изменения", command=lambda: threading.Thread(target=save_changes_thread).start())
button_save.pack(side=tk.LEFT, padx=5)

# Кнопка для сохранения закрытых профилей
button_save_closed_profiles = tk.Button(frame_input, text="Сохранить закрытые профили", command=lambda: threading.Thread(target=save_closed_profiles_thread).start())
button_save_closed_profiles.pack(side=tk.LEFT, padx=5)

# Фрейм для размещения combobox с использованием grid
combobox_frame = tk.Frame(root)
combobox_frame.pack(pady=10)

# Рамка для таблицы и скроллинг
frame_table = tk.Frame(root)
frame_table.pack(fill=tk.BOTH, expand=True)

# Горизонтальный скроллбар
scrollbar_x = tk.Scrollbar(frame_table, orient=tk.HORIZONTAL)
scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)

# Вертикальный скроллбар
scrollbar_y = tk.Scrollbar(frame_table, orient=tk.VERTICAL)
scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)

# Настройка таблицы для отображения данных
tree = ttk.Treeview(frame_table, show="headings", height=10, xscrollcommand=scrollbar_x.set, yscrollcommand=scrollbar_y.set)

scrollbar_x.config(command=tree.xview)
scrollbar_y.config(command=tree.yview)

# Добавление тега для закрытых профилей с цветом фона
tree.tag_configure('closed_profile', background=CLOSED_PROFILE_COLOR)

tree.pack(fill=tk.BOTH, expand=True)

# Текстовое поле для отображения журнала
log_frame = tk.Frame(root)
log_frame.pack(fill=tk.BOTH, expand=True, pady=10)
log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=8, state='disabled', font=("Courier", 10))
log_text.pack(fill=tk.BOTH, expand=True)

# Применение стилей к журналу
apply_log_styles()

# Создание контекстного меню для текстового поля журнала
context_menu = create_context_menu()
log_text.bind("<Button-3>", lambda event: context_menu.post(event.x_root, event.y_root))

# Список релевантных настроек, убран "page_access"
relevant_settings = [
    "profile",  # Кто видит основную информацию моей страницы
    "replies_view",  # Кто видит комментарии к записям
    "status_replies",  # Кто может комментировать мои записи
    "mail_send",  # Кто может писать мне личные сообщения
    "wall_send",  # Кто может оставлять записи на моей странице
    "wall"  # Кто видит чужие записи на моей странице
]

# Словарь для хранения переменных StringVar для каждого выпадающего меню
combobox_vars = {}
combobox_list = {}

# Запуск основного цикла приложения
root.mainloop()
